package com.ndbg.demo.service.cmbcpay;

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.*;

@Service
@Slf4j
public class OpenApiDemoSm2Service {
    /*** 加签私钥 */
    private final static String SM2_PRI_KEY = "07426ba4f0360ad883b2ed93860158169d42b15e3c6cf1d505e963dde0f7770d";
    /*** 商户公钥 */
    private final static String SM2_PUB_KEY = "04107d798a9ee73b109dcee80ef95b62143ffcd4258a500e7b29600003035911c007357db3034172bbcfa3616c4f4658352b77f50b49c89cb772d09baa4b18202e";
    /*** 银行公钥 */
//    private final static String SM2_CMB_PUB_KEY = "04b49c92296322b17cbf8b472a474998ae72d237aa14125c7925db846d43d2e37303e557800024af06cad7053596cd7371ce0d089cdbe666937acdd2ea780c23e9";
    private final static String SM2_CMB_PUB_KEY = "04e90f9f92db2763d3853fe2e9491e5475bc5fe731c214ed0f98e2a514d4f10c81a5f23b0f6db07ff444f6dcd57e69c4b3e05124cc3ef8b16da288d54744b88a1e";
    /*** 商户openApi申请所得appid */
    private static final String APP_ID = "7a587a2f-4e47-4eba-8228-12793af5c473";
    /*** 商户openApi申请所得secret */
    private static final String SECRET = "d3d362ab-454c-4f0e-bd50-80750ebc7ccf";
    /*** 摘要算法 */
    private static final String VERIFY = "SM3withSM2";
    /*** 收银台地址 */
    private static final String URL = "https://api.cmburl.cn:8065/skt/exima/ver1/addSingleBill";
    /*** sm2算法实例 */
    private static final SM2 SM2_INSTANCE = SmUtil.sm2(SM2_PRI_KEY, SM2_CMB_PUB_KEY);

    public JSONObject request(JSONObject reqVo) {
        String resp = httpClientPost(
                buildHeader(sm3DigestHex(reqVo.toJSONString())),
                reqVo.toJSONString(), URL);
        return JSON.parseObject(resp);
    }

    /*** SM2 加签 */
    public static String sm2SignHex(String dataStr) {
        // 用私钥对信息生成数字签名，签名格式为ASN1
        byte[] sign = SM2_INSTANCE.sign(dataStr.getBytes(StandardCharsets.UTF_8));
        // 在硬件签名中，返回结果为R+S，可以通过调用{@link cn.hutool.crypto.SmUtil#rsAsn1ToPlain(byte[])}方法转换之
        byte[] signature = SmUtil.rsAsn1ToPlain(sign);
        return HexUtil.encodeHexStr(signature);
    }

    /*** 国密网关body签名验签 */
    public static boolean sm2Verify(String msgStr, String signatureStr) {
        return SM2_INSTANCE.verify(msgStr.getBytes(StandardCharsets.UTF_8), SmUtil.rsPlainToAsn1(SmUtil.rsAsn1ToPlain(Base64Decoder.decode(signatureStr))));
    }

    /*** SM3摘要计算 */
    public static String sm3DigestHex(String src) {
        try {
            return SmUtil.sm3().digestHex(src);
        } catch (Exception e) {
            throw new RuntimeException("签名计算出现异常");
        }
    }

    /**
     * httpclient post request
     *
     * @param header http headers
     * @param body   http post body
     * @param url    http post url
     * @return
     */
    public String httpClientPost(Map<String, List<String>> header, String body, String url) {
        log.info("HttpVisitor body is {},header is {},url is {}", body, header, url);
        try {
            HttpResponse httpResp = HttpRequest.post(url)
                    .body(body, ContentType.JSON.toString())
                    .header(header)
                    .timeout(10000)
                    .setReadTimeout(30000)
                    .execute();
            if (200 != httpResp.getStatus()) {
                log.error("请求失败：status is [{}],message is [{}]", httpResp.getStatus(), httpResp.body());
                // TODO 商户自定义错误处理
                /*** 400 一般验签失败
                 * 401 api网关权限问题
                 * 403 一般为url、参数等错误
                 */
            }
            String cmbBodySign = httpResp.headers().get("CMB-BodySign").get(0);
            if (!StringUtils.isEmpty(cmbBodySign)) {
                // 商户验签
                boolean result = sm2Verify(httpResp.body(), cmbBodySign);
                if (!result) {
                    log.error("验签失败");
                    // TODO 商户自定义错误处理
                } else {
                    log.info("验签成功");
                }
            }
            String respBody = httpResp.body();
            log.info("HttpVisitor resp = {}", respBody);
            return respBody;
        } catch (Exception e) {
            log.error("Error: http post error: body is {},header is {},url is {}, e = ", body, header, url, e);
            throw e;
        }
    }

    /***
     * 生成国密密钥对
     * 此处需要单独使用此依赖
     * <dependency> * <groupId>org.bouncycastle</groupId>
     * <artifactId>bcprov-jdk15to18</artifactId>
     * <version>1.66</version>
     * </dependency>
     */
    public void generateSm2KeyPair() {
        //创建sm2 对象
        SM2 sm2 = SmUtil.sm2();
        //这里会自动生成对应的随机秘钥对 , 注意！ 这里一定要强转，才能得到对应有效的秘钥信息
        byte[] privateKey = BCUtil.encodeECPrivateKey(sm2.getPrivateKey());
        //这里公钥不压缩 公钥的第一个字节用于表示是否压缩 可以不要
        byte[] publicKey = ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false);
        System.out.println("私钥: " + HexUtil.encodeHexStr(privateKey).toUpperCase());
        System.out.println("公钥: " + HexUtil.encodeHexStr(publicKey).toUpperCase());
    }

    /**
     * 构建请求头
     *
     * @param sign 业务报文摘要
     *             * @return Http请求头
     */
    private Map<String, List<String>> buildHeader(String sign) {
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        Map<String, String> headerNeedMd5Data = new HashMap<>(4);
        headerNeedMd5Data.put("appid", APP_ID);
        headerNeedMd5Data.put("secret", SECRET);
        headerNeedMd5Data.put("sign", sign);
        headerNeedMd5Data.put("timestamp", timestamp);
        String headerNeedMd5SortStr = buildSortData(headerNeedMd5Data);
        Map<String, List<String>> header = new HashMap<>(4);
        header.put("appid", Collections.singletonList(APP_ID));
        header.put("timestamp", Collections.singletonList(timestamp));
        header.put("apisign", Collections.singletonList(sm2SignHex(headerNeedMd5SortStr)));
        header.put("sign", Collections.singletonList(sign));
        header.put("verify", Collections.singletonList(VERIFY));
        return header;
    }

    /**
     * 忽略大小写按照key1=value1&key2=value2组装参数
     */
    private String buildSortData(Map<String, String> map) {
        List<String> keys = new ArrayList<>(map.keySet());
        StringBuilder content = new StringBuilder();
        Collections.sort(keys);
        int index = 0;
        for (String key : keys) {
            String value = map.get(key);
            if (null != key && null != value) {
                content.append(index == 0 ? StrUtil.EMPTY : "&").append(key).append("=").append(value);
                index++;
            }
        }
        return content.toString();
    }

    public static void main(String[] args) {
        try {
            JSONObject reqVo = new JSONObject();
            reqVo.put("version", "2.0");
            reqVo.put("merchId", "JB0003");
            reqVo.put("orderId", "11111111111");
            reqVo.put("feeAmt", 1);
            reqVo.put("notifyUrl", "https://qr.alipay.com/bax067078eozcn3ekgag0072");


//            reqVo.put("merchId", "JB0003");
//            reqVo.put("branchNo", "0755");
//            reqVo.put("returnUrl", "https://qr.alipay.com/bax067078eozcn3ekgag0072");
//            reqVo.put("payNo", "123456789");
//            reqVo.put("payTypeList", new ArrayList<String>() {{
//                add("7");
//                add("8");
//            }});
//            reqVo.put("needCashier", "0");
            OpenApiDemoSm2Service openApiDemoService = new OpenApiDemoSm2Service();
            System.out.println("result is " + openApiDemoService.request(reqVo));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
